package terra.managers;

import java.util.ArrayList;

import org.newdawn.slick.Image;

import terra.Config;
import terra.model.Light;
import terra.model.WeatherSystem;
import terra.slick.GameState;

public class LightManager{

    private boolean colouredLights = true; 
   
    private float[][][][] lightValue; 
    
    private ArrayList<Light> lights = new ArrayList<Light>(); 
    private Light playerLight;
	private int heightInTiles;
	private int widthInTiles; 
	MapManager map = MapManager.getInstance();
	private static LightManager instance;

	private float knownLight = 0.00f;

	/**1 если блок проходимый, 0 если непроходимый, 2 если блок null*/
	public byte[][] boolMap;
	
	GameState game = GameState.getInstance();

	public int skipped;
	long timeskipped;
    //возвращает 0, если аргумент (x) равен нулю; -1, если x < 0 и 1, если x > 0.
	private int sign (int x) {
        return (x > 0) ? 1 : (x < 0) ? -1 : 0;
 	}
	
	public boolean drawBresenhamLine (int xstart, int ystart, int xend, int yend) {
	 /**
	  * xstart, ystart - начало;
	  * xend, yend - конец; 
	  */
		if (xstart < 0 || xstart > getWidthInTiles())
			return false;
		if (ystart < 0 || ystart > getHeightInTiles())
			return false;
		if (xend < 0 || xend > getWidthInTiles())
			return false;
		if (yend < 0 || yend > getHeightInTiles())
			return false;
		int x, y, dx, dy, incx, incy, pdx, pdy, es, el, err,i = 0;
		dx = xend - xstart;
		dy = yend - ystart;
		incx = sign(dx);
		incy = sign(dy);
		
		if (dx < 0) dx = -dx;
		if (dy < 0) dy = -dy;
		
		if (dx > dy){
			pdx = incx;
			pdy = 0;
			es = dy;
			el = dx;
		} else {
			pdx = 0;
			pdy = incy;
			es = dx;
			el = dy;
		}
		x = xstart;
		y = ystart;
		err = el/2;
		try{if (boolMap[x][y] == 0)i++;
		} catch(Exception e){return false;}
		for (int t = 0; t < el; t++) {
			err -= es;
			if (err < 0) {
				err += el;
				x += incx;//сдвинуть прямую (сместить вверх или вниз, если цикл проходит по иксам)
				y += incy;//или сместить влево-вправо, если цикл проходит по y
			} else {
				x += pdx;//продолжить тянуть прямую дальше, т.е. сдвинуть влево или вправо, если
				y += pdy;//цикл идёт по иксу; сдвинуть вверх или вниз, если по y
			}
			try{if (boolMap[x][y] == 0)i++;
			} catch(Exception e){return false;}
			if (i >= 2)
				return false;
		}
		return true;
	}
	
	
    public void updateLightMap() { 
    	if (Config.disableLighting)
    		return;
    	for (int y = 0;y <  getHeightInTiles(); y++) { 
            for (int x = 0;x < getWidthInTiles(); x++) { 
            	if (boolMap[x][y] == 2){
	            	for (int component = 0; component < 3; component++) {
	            		float light = GameState.getInstance().lightIntense;
	            		if (WeatherSystem.current != null)
	            			light*=WeatherSystem.lightIntense;
						lightValue[x][y][0][component] = light; // Top Left 
						lightValue[x][y][1][component] = light; // Top Right 
						lightValue[x][y][2][component] = light; // Bottom Left 
						lightValue[x][y][3][component] = light; // Bottom Right
					}
            	} else {
	            	for (int component = 0; component < 3; component++) {
						lightValue[x][y][0][component] = 0; // Top Left 
						lightValue[x][y][1][component] = 0; // Top Right 
						lightValue[x][y][2][component] = 0; // Bottom Left 
						lightValue[x][y][3][component] = 0; // Bottom Right
					}
            	}
            	if (boolMap[x][y] == 3){
            		//skipped++;
            		continue;
            	}
//            	if (timeskipped < System.currentTimeMillis()){
//            		timeskipped = System.currentTimeMillis()+1000;
//            		
//            	}
                for(int i = 0; i < getLights().size();i++) { 
                	Light light = getLights().get(i);
                	if (light == null)
                		continue;
                	if (light.getCenterX() < game.getOffsetX()-light.radius 
                			|| light.getCenterX()  > game.getOffsetX() + game.getGc().getWidth()+light.radius){
                		continue;
                	}
                	if (light.getCenterY() < game.getOffsetY()-light.radius
                			|| light.getCenterY()  > game.getOffsetY() + game.getGc().getHeight()+light.radius){
                		continue;
                	} 
                	if (distance((game.getTiledOffsetX()+x-4)*Config.tileSize, 
                			(game.getTiledOffsetY()+y-4)*Config.tileSize,
                				Config.tileSize, Config.tileSize, light.getCenterX(), light.getCenterY(), 0, 0) >= light.radius){
                		continue;
                	}

                    int lightTileCenterX = (int) (light.getCenterX()/Config.tileSize-game.getTiledOffsetX() + 4);
                 	int lightTileCenterY = (int) (light.getCenterY()/Config.tileSize-game.getTiledOffsetY() + 4);
                 	
                 	if (!drawBresenhamLine(lightTileCenterX,lightTileCenterY,x,y))
                        continue; 
                    int actualMinX = game.getTiledOffsetX()-4;
                    int actualMinY = game.getTiledOffsetY()-4;
                   
                    float[] effect; 
                    for (int component = 0; component < 3; component++) { 
                        // top left 
                        effect = light.getEffectAtCorner((x+actualMinX)*Config.tileSize, (y+actualMinY)*Config.tileSize, colouredLights); 
                        getLightValue()[x][y][0][component] += effect[component]; 
                        
                        // top right 
                        effect = light.getEffectAtCorner((x+actualMinX)*Config.tileSize+Config.tileSize, (y+actualMinY)*Config.tileSize, colouredLights); 
                        getLightValue()[x][y][1][component] += effect[component]; 
                        
                        // bottom left 
                        effect = light.getEffectAtCorner((x+actualMinX)*Config.tileSize, (y+actualMinY)*Config.tileSize+Config.tileSize, colouredLights); 
                        getLightValue()[x][y][2][component] += effect[component]; 
                        
                        // bottom right 
                        effect = light.getEffectAtCorner((x+actualMinX)*Config.tileSize+Config.tileSize, (y+actualMinY)*Config.tileSize+Config.tileSize, colouredLights); 
                        getLightValue()[x][y][3][component] += effect[component]; 
                    } 
                } 
            } 
        } 
    	//System.out.println("nano time per step " + (System.nanoTime()- start));
//    	System.out.println("LightManager: skipped "+ skipped);
//    	skipped = 0;
    }  
    
    public static void eraseBlend(Image bg) {
        bg.setColor(Image.TOP_LEFT, 1, 1, 1, 1); 
        bg.setColor(Image.TOP_RIGHT, 1, 1, 1, 1); 
        bg.setColor(Image.BOTTOM_LEFT, 1, 1, 1, 1); 
        bg.setColor(Image.BOTTOM_RIGHT, 1, 1, 1, 1);
    }
    
    public void blend(int curX, int curY, Image bg) {
    	if (Config.disableLighting)
    		return;
    	if (curX >= getWidthInTiles()-1)
    		return;
    	if (curY >= getHeightInTiles()-1)
    		return; 
    	if (curX < 0 || curY < 0)
    		return;
    	 float[][] val = getLightValue()[curX][curY]; 
    	 //System.out.println(curX+"____"+curY);
        if(curX == 0){curX++;} 
        if(curY == 0){curY++;} 
        float topLeftRed = (getLightValue()[curX - 1][curY - 1][3][0] 
                + getLightValue()[curX][curY - 1][2][0] 
                + getLightValue()[curX - 1][curY][1][0] 
                + getLightValue()[curX - 1][curY][3][0] 
                + getLightValue()[curX][curY - 1][3][0] 
                + val[0][0] + val[1][0] + val[2][0] + val[3][0]) / 9; 

        float topLeftGreen = (getLightValue()[curX - 1][curY - 1][3][1] 
                + getLightValue()[curX][curY - 1][2][1] 
                + getLightValue()[curX - 1][curY][1][1] 
                + getLightValue()[curX - 1][curY][3][1] 
                + getLightValue()[curX][curY - 1][3][1] 
                + val[0][1] + val[1][1] + val[2][1] + val[3][1]) / 9; 

        float topLeftBlue = (getLightValue()[curX - 1][curY - 1][3][2] 
                + getLightValue()[curX][curY - 1][2][2] 
                + getLightValue()[curX - 1][curY][1][2] 
                + getLightValue()[curX - 1][curY][3][2] 
                + getLightValue()[curX][curY - 1][3][2] 
                + val[0][2] + val[1][2] + val[2][2] + val[3][2]) / 9; 



        // ------------------- 

        float topRightRed = (getLightValue()[curX][curY - 1][2][0] 
                + getLightValue()[curX][curY - 1][3][0] 
                + getLightValue()[curX + 1][curY - 1][2][0] 
                + getLightValue()[curX + 1][curY][0][0] 
                + getLightValue()[curX + 1][curY][2][0] 
                + val[0][0] + val[1][0] + val[2][0] + val[3][0]) / 9; 

        float topRightGreen = (getLightValue()[curX][curY - 1][2][1] 
                + getLightValue()[curX][curY - 1][3][1] 
                + getLightValue()[curX + 1][curY - 1][2][1] 
                + getLightValue()[curX + 1][curY][0][1] 
                + getLightValue()[curX + 1][curY][2][1] 
                + val[0][1] + val[1][1] + val[2][1] + val[3][1]) / 9; 

        float topRightBlue = (getLightValue()[curX][curY - 1][2][2] 
                + getLightValue()[curX][curY - 1][3][2] 
                + getLightValue()[curX + 1][curY - 1][2][2] 
                + getLightValue()[curX + 1][curY][0][2] 
                + getLightValue()[curX + 1][curY][2][2] 
                + val[0][2] + val[1][2] + val[2][2] + val[3][2]) / 9; 

        // ------------------- 

        float bottomLeftRed = (getLightValue()[curX - 1][curY][3][0] 
                + getLightValue()[curX - 1][curY][1][0] 
                + getLightValue()[curX - 1][curY + 1][1][0] 
                + getLightValue()[curX][curY + 1][0][0] 
                + getLightValue()[curX][curY + 1][1][0] 
                + val[0][0] + val[1][0] + val[2][0] + val[3][0]) / 9; 

        float bottomLeftGreen = (getLightValue()[curX - 1][curY][3][1] 
                + getLightValue()[curX - 1][curY][1][1] 
                + getLightValue()[curX - 1][curY + 1][1][1] 
                + getLightValue()[curX][curY + 1][0][1] 
                + getLightValue()[curX][curY + 1][1][1] 
                + val[0][1] + val[1][1] + val[2][1] + val[3][1]) / 9; 

        float bottomLeftBlue = (getLightValue()[curX - 1][curY][3][2] 
                + getLightValue()[curX - 1][curY][1][2] 
                + getLightValue()[curX - 1][curY + 1][1][2] 
                + getLightValue()[curX][curY + 1][0][2] 
                + getLightValue()[curX][curY + 1][1][2] 
                + val[0][2] + val[1][2] + val[2][2] + val[3][2]) / 9; 

        // ------------------- 

        float bottomRightRed = (getLightValue()[curX][curY + 1][0][0] 
                + getLightValue()[curX][curY + 1][1][0] 
                + getLightValue()[curX + 1][curY][0][0] 
                + getLightValue()[curX + 1][curY][2][0] 
                + getLightValue()[curX + 1][curY + 1][0][0] 
                + val[0][0] + val[1][0] + val[2][0] + val[3][0]) / 9; 

        float bottomRightGreen = (getLightValue()[curX][curY + 1][0][1] 
                + getLightValue()[curX][curY + 1][1][1] 
                + getLightValue()[curX + 1][curY][0][1] 
                + getLightValue()[curX + 1][curY][2][1] 
                + getLightValue()[curX + 1][curY + 1][0][1] 
                + val[0][1] + val[1][1] + val[2][1] + val[3][1]) / 9; 

        float bottomRightBlue = (getLightValue()[curX][curY + 1][0][2] 
                + getLightValue()[curX][curY + 1][1][2] 
                + getLightValue()[curX + 1][curY][0][2] 
                + getLightValue()[curX + 1][curY][2][2] 
                + getLightValue()[curX + 1][curY + 1][0][2] 
                + val[0][2] + val[1][2] + val[2][2] + val[3][2]) / 9; 
	        bg.setColor(Image.TOP_LEFT, topLeftRed, topLeftGreen, topLeftBlue, 1); 
	        bg.setColor(Image.TOP_RIGHT, topRightRed, topRightGreen, topRightBlue, 1); 
	        bg.setColor(Image.BOTTOM_LEFT, bottomLeftRed, bottomLeftGreen, bottomLeftBlue, 1); 
	        bg.setColor(Image.BOTTOM_RIGHT, bottomRightRed, bottomRightGreen, bottomRightBlue, 1);
    } 
	
	public static LightManager getInstance()
	{
		if (instance == null)
			return instance = new LightManager();
		return instance;
		
	}


	public void setWidthInTiles(int widthInTiles) {
		this.widthInTiles = widthInTiles;
	}


	public int getWidthInTiles() {
		return widthInTiles;
	}


	public void setHeightInTiles(int heightInTiles) {
		this.heightInTiles = heightInTiles;
	}


	public int getHeightInTiles() {
		return heightInTiles;
	}


	public void setLightValue(float[][][][] lightValue) {
		this.lightValue = lightValue;
	}


	public float[][][][] getLightValue() {
		return lightValue;
	}


	public void setPlayerLight(Light playerLight) {
		this.playerLight = playerLight;
	}


	public Light getPlayerLight() {
		return playerLight;
	}


	public void setLights(ArrayList<Light> lights) {
		this.lights = lights;
	}


	public ArrayList<Light> getLights() {
		return lights;
	}


	public void setKnownLight(float knownLight) {
		this.knownLight = knownLight;
	}


	public float getKnownLight() {
		return knownLight;
	}
	
	public static double distance(float x1, float y1,float w1, float h1,float x2, float y2,float w2, float h2) {
		float dx = (x1+w1/2) - (x2+w2/2);
		float dy = (y1+h1/2) - (y2+h2/2);
		return Math.sqrt((dx*dx)+(dy*dy));
	}
    
}
